#ifndef TC_MSG_CONTROLLER_HPP
#define TC_MSG_CONTROLLER_HPP

#ifndef TC_DB_MAX_W
  #define TC_DB_MAX_W 130 // Maximum path loss (use in msg parsing)
#endif

#include "tc_db_manager.hpp"
#include "tc_types.hpp" // p_msg and r_msg
#include "tc_stats_collector.hpp"

#include <string>
#include <sstream>
#include <vector>


using std::string;
using std::stringstream;


//////////////////////////////////////////////////////////
// Message Controller class

class TCMessageController{
public:
  // Default constructor (use other constructor instead)
  TCMessageController() {m_tc_db = NULL; m_stats = NULL;}

  // Constructor with initialization
  TCMessageController(TCDatabaseManager& tc_db, TCStatsCollector& stats)
  {Init(tc_db, stats);}

  // Set Database Manager and initialize
  void Init(TCDatabaseManager& tc_db, TCStatsCollector& stats)
  {m_tc_db = &tc_db; m_stats = &stats; AssemblePmsg();}

  // Decode message and take appropriate action
  bool ParseMessage(string& msg);

  // Decode message and take appropriate action (c-string buffer version)
  bool ParseMessage(const char* buf, int len);

  // Assemble P_MSG
  bool AssembleMessage(p_msg& msg);

  // Assemble R_MSG
  // note: the maximum length of the r_msg should be large enough to hold the neighbor list
  // tip: call tc_db.GetNeighborhood() beforehand to get the actual size of the neighbor list
  bool AssembleMessage(r_msg& msg, int max_size);

private:
  p_msg m_p_msg;
  string m_ip_addr, m_mac_addr;
  TCDatabaseManager* m_tc_db;
  TCStatsCollector* m_stats;

  void AssemblePmsg();

  void ParsePmsg(const char* buf, int len);

  void ParseRmsg(const char* buf, int len);

  double GetStats(string ip) {
    if (m_stats == NULL) return -1;

    return m_stats->GetLinkQuality(ip);
  }
};


///////////
bool TCMessageController::ParseMessage(string& msg)
{
  if (m_tc_db == NULL){
    return false;
  }
  
  return ParseMessage(msg.c_str(), msg.size());
}

///////////
bool TCMessageController::ParseMessage(const char* buf, int len)
{
  if (m_tc_db == NULL){
    return false;
  }

  uint16_t msg_type; // Type of received message

  // Get message type
  char_ntoh(&buf[0], &msg_type, 2);

  // Received a P_MSG	
  if( msg_type == P_MSG ){
    ParsePmsg(buf, len);
    return true;
  }

  if( msg_type == R_MSG ){
    ParseRmsg(buf, len);
    return true;
  }

  // Message type is unknown
  return false;
}

//////////
void TCMessageController::ParsePmsg(const char* buf, int len)
{
  ssize_t i = 0; // Buffer index
  p_msg pMsg;

  if (len < (2 + pMsg.NODE.size())){
    std::cout << "ParsePmsg: buffer length is too small" << std::endl;
    return;
  }

  pMsg.TYPE = P_MSG;  i += 2;
  i += pMsg.NODE.init(&buf[i]);

  // ADD INFORMATION INTO THE DATABASE MANAGER
  string ip, mac; bool tpc; int max_power;
  pMsg.NODE.get(ip, mac, tpc, max_power);
  m_tc_db->AddNode(ip, mac, tpc, max_power); // add node

  // Get edge weight from the stats collector (use mac address)
  double w = GetStats(ip); // GetStats() returns the received power in dBm
  if (w != -1){
    w = max_power - w; // w = path loss
  }
  else{ // w == -1
    w = TC_DB_MAX_W;
  }

  m_tc_db->AddEdge(ip, m_ip_addr, w); // add in_edge with weight
}

//////////
void TCMessageController::ParseRmsg(const char* buf, int len)
{
  ssize_t i = 0; // Buffer index
  uint16_t num_nbr; // Number of neighbors
  r_msg rMsg;

  int size_of_neighbor_t = rMsg.NEIGHBOR->size();
  int size_of_weight_t = (int) sizeof(int16_t);
  
  // Get number of neighbors in the R_MSG (LEN field)
  i = 2;
  char_ntoh(&buf[i], &num_nbr, 2);

  // Debugging...
  //std::cout << std::endl << "SIZE = " << rMsg.NEIGHBOR->size() << std::endl;

  // Check if we have a truncated message (num_nbr != actual number of neighbors)
  //  if (len < (4 + (int) num_nbr * (rMsg.NEIGHBOR->size() + (int) sizeof(int16_t)))){
  if (len < (4 + (int) num_nbr * (size_of_neighbor_t + size_of_weight_t))){
    std::cout << "ParseRmsg: message is truncated (LEN = ";
    std::cout << num_nbr << " ; decoding data from ";

    // Calculate how much neighbor information we actually have
    //num_nbr = (ssize_t) ((len - 4) / (rMsg.NEIGHBOR->size() + sizeof(int16_t)));
    num_nbr = (ssize_t) ((len - 4) / (size_of_neighbor_t + size_of_weight_t));

    std::cout << num_nbr << " neighbors)" << std::endl;
  }

  // Allocate memory for the neighbor list and weight list in the R_MSG (variable length)
  rMsg.NEIGHBOR = (tc_node *) calloc(1, num_nbr * sizeof(tc_node));
  rMsg.WEIGHT = (int16_t *) calloc(1, num_nbr * sizeof(int16_t));

  // Decoding the received R_MSG
  rMsg.TYPE = R_MSG;
  rMsg.LEN = num_nbr;

  i = 4;
  for(int j = 0; j < (int) num_nbr; ++j){
    i += rMsg.NEIGHBOR[j].init(&buf[i]); // neighbor
    char_ntoh(&buf[i], &rMsg.WEIGHT[j], sizeof(int16_t)); // edge weight
    // rMsg.WEIGHT[j] = &buf[i]; // use this if weight is of char type
    i += sizeof(int16_t);
  }

  // ADD INFORMATION INTO THE DATABASE MANAGER
  // NOTE: we are updating the node information and also the in_edge information
  //       of the owner of the R_MSG

  string u, v, mac; bool tpc; int max_power;

  rMsg.NEIGHBOR[0].get(v, mac, tpc, max_power);

  // Update node info and in_edge info
  double w = GetStats(v); // get edge weight
  if (w != -1){
    w = max_power - w; // w = path loss
  }
  else{ // w == -1
    w = TC_DB_MAX_W;
  }

  m_tc_db->AddNode(v, mac, tpc, max_power); // update node info
  m_tc_db->AddEdge(v, m_ip_addr, w); // update in_edge with weight

  for (int j = 1; j < (int) num_nbr; ++j){
    rMsg.NEIGHBOR[j].get(u, mac, tpc, max_power);
    m_tc_db->AddEdge(u, v, (double) rMsg.WEIGHT[j]); // add edge (u,v) with weight
  }
  
  // Deallocate memory for the neighbor list and the weight list
  free(rMsg.NEIGHBOR);
  free(rMsg.WEIGHT);
}

//////////
bool TCMessageController::AssembleMessage(p_msg& msg)
{
  if (m_tc_db == NULL){
    return false;
  }

  msg = m_p_msg;
  return true;
}

//////////
bool TCMessageController::AssembleMessage(r_msg& msg, int max_size)
{
  string ip, mac;
  bool tpc;
  int max_power;

  std::vector<string> ip_list;
  std::vector<string>::iterator s_iter;
  std::vector<double> w_list;
  std::vector<double>::iterator w_iter;

  if (m_tc_db == NULL){
    return false;
  }

  // Get information about node 0 (i.e., self)
  m_tc_db->GetNodeInfo(0, ip, mac, tpc, max_power);


  if (!m_tc_db->GetNeighborhood(ip, ip_list, w_list)){
    return false;
  }

  if ( (1 + ip_list.size()) > (unsigned int) max_size ){
    std::cout << "TCMessageController: r_msg size is too small";
    std::cout << std::endl;
    return false;
  }

  // Write r_msg
  msg.TYPE = R_MSG;
  msg.LEN = 1 + ip_list.size();

  msg.NEIGHBOR[0].init(ip.c_str(), mac.c_str(), tpc, max_power);
  msg.WEIGHT[0] = -1; // -1 means an invalid edge (in this case, a loop)

  int i = 1; w_iter = w_list.begin();
  for (s_iter = ip_list.begin(); s_iter != ip_list.end(); ++s_iter, ++w_iter, ++i){
    // Get neighbor information
    m_tc_db->GetNodeInfo(*s_iter, ip, mac, tpc, max_power);
    msg.NEIGHBOR[i].init(ip.c_str(), mac.c_str(), tpc, max_power);
    msg.WEIGHT[i] = (int16_t) *w_iter;
  }

  return true;
}

//////////
void TCMessageController::AssemblePmsg()
{
  string ip, mac;
  bool tpc;
  int max_power;

  if (m_tc_db == NULL){
    return;
  }
  
  // Get information about node 0 (i.e., self)
  m_tc_db->GetNodeInfo(0, ip, mac, tpc, max_power);

  // Write p_msg
  m_p_msg.TYPE = P_MSG;
  m_p_msg.NODE.init(ip.c_str(), mac.c_str(), tpc, max_power);

  // Set IP and MAC address members
  m_ip_addr = ip;
  m_mac_addr = mac;
}

#endif // TC_MSG_CONTROLLER_HPP
